package com.duff.download.okdownload;

import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;

import com.duff.download.okdownload.interfaces.OnDownloadListener;
import com.duff.download.okdownload.utils.CertificateUtils;
import com.duff.download.okdownload.utils.FileUtils;
import com.duff.download.okdownload.utils.Logger;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.X509TrustManager;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Headers;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

/**
 * author：duff
 * version：1.0.0
 * date：2017/8/27
 * desc：基于OkHttp3实现的一个下载类
 */
public class OkDownload {
    public static final String SUFFIX_TMP = ".tmp"; // 下载临时文件后缀
    public static final long DEFAULT_TIMEOUT = 10 * 1000; //默认的超时时间
    /**
     * 文件读写缓冲大小
     */
    public static final int DOWNLOAD_BUFFER_SIZE = 4096;

    private Handler mHandler = new Handler(Looper.getMainLooper());

    private OkHttpClient mOkHttpClient;
    private OkHttpClient.Builder mOkHttpClientBuilder;
    private Request.Builder mRequestBuilder;

    private String mUrl;
    private Object mTag;
    private String mPath;
    private boolean mIsAppend; // 写入文件时是否从末尾追加(断点续传时必须为true)
    private SSLSocketFactory mSslSocketFactory;
    private X509TrustManager mTrustManager;
    private HostnameVerifier mHostnameVerifier;
    private long mConnectTimeout = DEFAULT_TIMEOUT;
    private long mReadTimeout = DEFAULT_TIMEOUT;
    private long mWriteTimeout = DEFAULT_TIMEOUT;

    private boolean mIsCanceled;


    private OkDownload(Builder builder) {
        mUrl = builder.url;
        mTag = builder.tag;
        mPath = builder.path;
        mIsAppend = builder.isAppend;
        mConnectTimeout = builder.connectTimeout;
        mReadTimeout = builder.readTimeout;
        mWriteTimeout = builder.writeTimeout;
        mSslSocketFactory = builder.sslSocketFactory;
        mTrustManager = builder.trustManager;
        mHostnameVerifier = builder.hostnameVerifier;

        mOkHttpClientBuilder = new OkHttpClient.Builder();
        mOkHttpClientBuilder.sslSocketFactory(mSslSocketFactory, mTrustManager);
        mOkHttpClientBuilder.hostnameVerifier(mHostnameVerifier);
        mOkHttpClientBuilder.connectTimeout(mConnectTimeout, TimeUnit.MILLISECONDS);
        mOkHttpClientBuilder.readTimeout(mReadTimeout, TimeUnit.MILLISECONDS);
        mOkHttpClientBuilder.writeTimeout(mWriteTimeout, TimeUnit.MILLISECONDS);
        mOkHttpClient = mOkHttpClientBuilder.build();

        mRequestBuilder = new Request.Builder();
        mRequestBuilder.get()
                .url(mUrl)
                .tag(mTag);

        HashMap<String, String> header = builder.header;
        if (header != null) {
            Iterator iterator = header.keySet().iterator();
            while (iterator.hasNext()) {
                String key = iterator.next().toString();
                String value = header.get(key);
                mRequestBuilder.header(key, value);
            }
        }
        if (builder.headers != null) {
            mRequestBuilder.headers(builder.headers);
        }
    }

    public OkHttpClient getOkHttpClient() {
        return mOkHttpClient;
    }

    public void setOkHttpClient(OkHttpClient okHttpClient) {
        this.mOkHttpClient = okHttpClient;
    }

    public boolean isAppend() {
        return mIsAppend;
    }

    public void cancel() {
        mIsCanceled = true;
    }

    private void checkUrl() {
        if (TextUtils.isEmpty(mUrl)) {
            try {
                throw new Exception("Invalid download url!");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        Logger.d("Download url:" + mUrl);
    }

    public void download(Callback callback) {
        checkUrl();

        final Request request = mRequestBuilder.build();
        mOkHttpClient.newCall(request).enqueue(callback);
    }

    public void download(OnDownloadListener listener) {
        if (TextUtils.isEmpty(mPath)) {
            try {
                throw new Exception("Invalid save path!");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        download(mPath, listener);
    }

    public void download(final String path, final OnDownloadListener listener) {
        checkUrl();

        final Request request = mRequestBuilder.build();
        final String url = request.url().toString();
        // 开始
        notifyDownloadStart(url, listener);

        mOkHttpClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Logger.e("download failed !!!");
                notifyDownloadError(url, e, listener);
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                try {
                    final File targetFile = new File(path + SUFFIX_TMP);
                    if (!targetFile.exists()) {
                        FileUtils.createFile(targetFile.getAbsolutePath());
                    }

                    if (response.isSuccessful() && writeStream2File(response, targetFile, listener)) {
                        notifyDownloadSuccess(url, new File(path), listener);
                    } else {
                        notifyDownloadError(url, new Exception("Download not completed !!!"), listener);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    notifyDownloadError(url, e, listener);
                }
            }
        });
    }

    public static void download(final String url, final String path, final OnDownloadListener listener) {
        Builder builder = new Builder();
        builder.url(url).tag(url);
        builder.build().download(path, listener);
    }


    /**
     * 同步下载，部分场景可能需要
     *
     * @param url
     * @param path
     * @return
     */
    public static File downloadSync(final String url, final String path) {
        return downloadSync(url, path, null);
    }

    public static File downloadSync(final String url, final String path, final OnDownloadListener listener) {
        Builder builder = new Builder();
        builder.url(url).tag(url);
        return builder.build().downloadSync(path, listener);
    }

    public File downloadSync(final String path, final OnDownloadListener listener) {
        checkUrl();
        final Request request = mRequestBuilder.build();
        final String url = request.url().toString();

        // 开始
        notifyDownloadStart(url, listener);

        try {
            Response response = mOkHttpClient.newCall(request).execute(); // 同步请求
            if (response != null && response.body() != null && response.isSuccessful()) {
                try {
                    final File targetFile = new File(path + SUFFIX_TMP);
                    if (!targetFile.exists()) {
                        FileUtils.createFile(targetFile.getAbsolutePath());
                    }
                    if (response.isSuccessful() && writeStream2File(response, targetFile, listener)) {
                        notifyDownloadSuccess(url, new File(path), listener);
                        return new File(path);
                    } else {
                        notifyDownloadError(url, new Exception("Download not completed !!!"), listener);
                        return null;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    notifyDownloadError(url, e, listener);
                    return null;
                }
            }
            return null;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    /*
    * 写入文件
    * */
    private boolean writeStream2File(Response response, File localFile, OnDownloadListener listener) {
        InputStream inputStream = response.body().byteStream();
        if (inputStream != null) {
            String url = response.request().url().toString();
            FileOutputStream buffer = null;
            boolean isErrorOccurs = false;
            long totalBytes = response.body().contentLength();
            long downloaded = 0;

            try {
                buffer = new FileOutputStream(localFile);
                byte[] buf = new byte[DOWNLOAD_BUFFER_SIZE];
                int len = 0;

                while ((len = inputStream.read(buf)) != -1 && !mIsCanceled) {
                    downloaded += len;
                    buffer.write(buf, 0, len);
                    notifyDownloadProgress(url, downloaded, totalBytes, localFile, listener);
                }
            } catch (IOException e) {
                Logger.e(e.toString());
                isErrorOccurs = true;
            } finally {
                if (!mIsCanceled && !isErrorOccurs) {
                    notifyDownloadProgress(url, downloaded, totalBytes, localFile, listener);

                    String tmpFilePath = localFile.getAbsolutePath();
                    // 下载完成后去除临时文件后缀
                    localFile.renameTo(new File(tmpFilePath.substring(0, tmpFilePath.indexOf(SUFFIX_TMP))));
                }
                try {
                    if (inputStream != null) {
                        inputStream.close();
                    }
                    if (buffer != null) {
                        buffer.flush();
                        buffer.close();
                    }
                } catch (IOException e) {
                    Logger.e(e.toString());
                }
            }
            return !mIsCanceled && !isErrorOccurs;
        }
        return false;
    }

    // OkDownload 创建类
    public static final class Builder {
        SSLSocketFactory sslSocketFactory;
        X509TrustManager trustManager;
        HostnameVerifier hostnameVerifier;
        String url;
        Object tag;
        String path;
        HashMap<String, String> header;
        Headers headers;
        boolean isAppend;
        long connectTimeout;
        long readTimeout;
        long writeTimeout;

        public Builder() {
            CertificateUtils.SSLParams sslParams = CertificateUtils.getSslSocketFactory();
            this.sslSocketFactory = sslParams.sSLSocketFactory;
            this.trustManager = sslParams.trustManager;
            this.hostnameVerifier = CertificateUtils.UnSafeHostnameVerifier;
            this.tag = "download-default-tag";
            this.isAppend = false;
            this.connectTimeout = DEFAULT_TIMEOUT;
            this.readTimeout = DEFAULT_TIMEOUT;
            this.writeTimeout = DEFAULT_TIMEOUT;
            this.header = new HashMap<>();
        }

        public Builder sslSocketFactory(SSLSocketFactory sslSocketFactory, X509TrustManager trustManager) {
            this.sslSocketFactory = sslSocketFactory;
            this.trustManager = trustManager;

            return this;
        }

        public Builder hostnameVerifier(HostnameVerifier hostnameVerifier) {
            this.hostnameVerifier = hostnameVerifier;

            return this;
        }

        public Builder url(String url) {
            this.url = url;

            return this;
        }

        public Builder tag(Object tag) {
            this.tag = tag;

            return this;
        }

        public Builder path(String path) {
            this.path = path;

            return this;
        }

        public Builder isAppend(boolean append) {
            this.isAppend = append;

            return this;
        }

        public Builder header(String key, String value) {
            this.header.put(key, value);

            return this;
        }

        public Builder headers(Headers headers) {
            this.headers = headers;

            return this;
        }

        public Builder connectTimeout(long timeout) {
            this.connectTimeout = timeout;

            return this;
        }

        public Builder readTimeout(long timeout) {
            this.readTimeout = timeout;

            return this;
        }

        public Builder writeTimeout(long timeout) {
            this.writeTimeout = timeout;

            return this;
        }

        public OkDownload build() {
            return new OkDownload(this);
        }
    }

    // 下载开始
    private void notifyDownloadStart(final String url, final OnDownloadListener listener) {
        if (listener != null) {
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    listener.onStart(url);
                }
            });
        }
    }

    // 下载出错
    private void notifyDownloadError(final String url, final Exception e, final OnDownloadListener listener) {
        if (listener != null) {
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    listener.onError(url, e);
                }
            });
        }
    }

    // 下载进度
    private void notifyDownloadProgress(final String url, final long downloaded, final long total, final File file, final OnDownloadListener listener) {
        if (listener != null) {
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    listener.onProgress(url, downloaded, total, file);
                }
            });
        }
    }

    // 下载成功
    private void notifyDownloadSuccess(final String url, final File file, final OnDownloadListener listener) {
        if (listener != null) {
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    listener.onSuccess(url, file);
                }
            });
        }
    }

}
